﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Noris.Tools.TraceVisualiser.Support;
using Noris.Tools.TraceVisualiser.Components;

namespace Noris.Tools.TraceVisualiser.TraceData
{
    #region NodeInfo : objekt, který představuje jeden řádek = bnode grafu. Node může obsahovat události (elementy) i další subnody (Childs).
    /// <summary>
    /// NodeInfo : objekt, který představuje jeden řádek = node grafu. Node může obsahovat události (elementy) i další subnody (Childs).
    /// </summary>
    public class NodeInfo : BaseInfo, IDisposable
    {
        #region Základní konstrukce
        protected NodeInfo(MainInfo main, NodeInfo parent, string nodeKey)
            : base(main, nodeKey)
        {
            this._Initialize(parent);
        }
        protected NodeInfo(MainInfo main, NodeInfo parent, TreePathItem treeItem)
            : base(main, treeItem.Key)
        {
            this._Initialize(parent);

            this.Texts = new string[] { treeItem.Text };
            if (treeItem.RowDeclaration != null)
                this.RowDeclaration = treeItem.RowDeclaration;
        }
        public NodeInfo(MainInfo main, NodeInfo parent, string nodeKey, string[] texts, TraceVisualiser.Components.RowGraphMode rowGraph, RowProperties properties)
            : base(main, nodeKey)
        {
            this._Initialize(parent);

            this.Texts = texts;
            this.RowDeclaration.Properties = properties;
            this.RowDeclaration.RowGraph = rowGraph;
        }
        private void _Initialize(NodeInfo parent)
        {
            this.Parent = parent;
            this.Newcomer = true;
			this.IsRoot = (parent == null);

            // Jedinečná instance Root:
			if (this.IsRoot)
				this.Root = new RootInfo(this);
			else
                this.Root = parent.Root;
			
            this.Childs = new CollectIdx<NodeInfo>();
            this.Elements = new Dictionary<GID, IDataElement>();
            this.RowDeclaration = new GraphRowDeclarationCls();
            this.Root.AddToIndex(this);
        }
        public override string ToString()
        {
			return "Node: GID=" + this.GId.ToString() + "; Text=" + this.Texts[0] + "; Newcomer=" + (this.Newcomer ? "true" : "false") + "; Childs:" + this.Childs.Count.ToString() + "; Elements:" + this.Elements.Count.ToString();
        }
        /// <summary>
        /// Číslo třídy pro GID
        /// </summary>
        public override int ClassNr { get { return Constants.ClassNumberNode; } }
        /// <summary>
        /// Parent node = ten, do něhož patřím
        /// </summary>
        public NodeInfo Parent { get; private set; }
        /// <summary>
        /// Soupis podřízených nodes
        /// </summary>
        public CollectIdx<NodeInfo> Childs { get; private set; }
        /// <summary>
        /// Soupis událostí tohoto node
        /// </summary>
        public virtual Dictionary<GID, IDataElement> Elements { get; private set; }
        #endregion
        #region Dispose
        public override void Dispose()
        {
            foreach (NodeInfo child in this.Childs)
                child.Dispose();
            
            this.Childs.Clear();
            this.Elements.Clear();
            this.RowDeclaration = null;
            this.Texts = null;
        }
        #endregion
        #region Property ve významu Node
        /// <summary>
        /// Obsahuje počet nodů (this + childs + rekurzivně)
        /// </summary>
        public int NodeCount
        {
            get
            {
                int cnt = 1;
                foreach (NodeInfo node in this.Childs)
                    cnt += node.NodeCount;
                return cnt;
            }
        }
        /// <summary>
        /// Obsahuje počet elementů (this + childs + rekurzivně)
        /// </summary>
        public int NodeElementsCount
        {
            get
            {
                int cnt = this.Elements.Count;
                foreach (NodeInfo node in this.Childs)
                    cnt += node.NodeElementsCount;
                return cnt;
            }
        }
        /// <summary>
        /// Obsahuje plný klíč = počínaje od Rootu až k this.Key, oddělovačem je zpětné lomítko. Na začátku není ani zpětné lomítko, ani Key prvku Root.
        /// </summary>
        public string FullKey
        {
            get
            {
                string fullKey = "";
                if (!this.IsRoot)
                {
                    if (this.HasParent)
                        fullKey = this.Parent.FullKey + "\\";
                    if (!String.IsNullOrEmpty(this.Key))
                        fullKey += this.Key;
                }
                return fullKey;
            }
        }
		/// <summary>
		/// Typ trace souboru, který tento node zavedl.
		/// Typ je určen v readeru při detekci souboru, a je pro všechny nody vytvořené jedním souborem (= readerem) shodný.
		/// Není možno, aby jeden reader vytvořil nody více typů.
		/// Typ následně slouží k tomu, aby při on-demand načítání obsahu se načítaly jen ty soubory, které budou do daného nodu vkládat svůj obsah.
		/// </summary>
		public string TraceType
		{
			get
			{
				if (this._TraceType == null && this.Parent != null)
					return this.Parent.TraceType;
				return this._TraceType;
			}
			set
			{
				this._TraceType = value;
			}
		}
		private string _TraceType;
        #endregion
        #region Správa nových nodů a nodů se změnu v elementech. Tvorba informace o změnách.
        /// <summary>
        /// Tento node je zde nováček. Zatím nebyl předán do grafické vrstvy.
        /// Je nově založen, ale grafika o něm neví.
        /// V nejbližší metodě Root.CollectChanges() bude detekován a oznámen.
        /// </summary>
        public bool Newcomer { get; private set; }
        /// <summary>
        /// Příznak, že grafická vrstva si z tohoto nodu již někdy vyžádala jeho Childs (metodou this.CreateChildsRow()).
        /// Pokud ano (true), pak musíme grafickou vrstvu informovat o nově založených nodech, protože tam dosud nejsou, a grafická vrstva si je sama žádat nebude (myslí si, že nové už nejsou).
        /// Pokud ne, pak do grafické vrstvy nemusíme předávat data o změnách, protože si o naše Childs teprve v budoucnu řekne.
        /// </summary>
        public bool ChildRowsCreated { get; private set; }
        /// <summary>
        /// Tento node obsahuje elementy, které se změnily od doby, kdy byly naposledy předány do grafické vrstvy.
        /// V nejbližší metodě Root.CollectChanges() bude tento node detekován a oznámen.
        /// Pokud je element přidán nebo odebrán, node to detekuje sám (nody se přidávají metodou this.ElementAdd(), ta si to ohlídá.
        /// Pokud se mění data v elementu, to si tento node nedokáže ohlídat. Proto to element musí oznámit osobně, volá metodu ElementChange(GID).
        /// Node nemá přesnou evidenci změněných elementů, pouze příznak bool (změna Ano/Ne).
        /// </summary>
        public bool ContainChangeInThisElements { get; private set; }
        /// <summary>
        /// Příznak, že tento node má ve svých vlastních Childs nějaký, který je Newcomer.
        /// Pokud this sám je Newcomer, pak zde vrací false - i kdyby obsahoval všechny svoje Childs nové.
        /// Pokud nějaký z mých starých Child obsahuje nový Child, pak se zde vrací false.
        /// </summary>
        public bool ContainNewChilds
        {
            get
            {
                if (this.Newcomer) return false;
				return this.Childs.Any(ch => ch.Newcomer);          // Vracím true, pokud ve svých Childs najdu takový, který je Newcomer.
            }
        }
        /// <summary>
        /// Do předaného argumentu vepíše všechny změny, které jsou v tomto nodu a v nodech jemu podřízených (rkurzivně volá tuto metodu).
        /// Změny vkládá "od vrchu" = nejdřív sebe, a pak své Childs.
        /// </summary>
        public void CollectChanges(NodeChangeCollect changes, bool clearIndicators)
        {
            // Pokud mám změnu ve svých elementech, nahlásím to:
            if (this.ContainChangeInThisElements)
            {
                changes.NodeWithElementChangedList.Add(this.GId);
                if (clearIndicators)
                    this.ContainChangeInThisElements = false;
            }

            // Pokud jsem svoje Childs ještě neposílal do grafické vrstvy, další věci neřeším:
            if (!this.ChildRowsCreated)
                return;

            // Pokud mezi mými Childs je nějaký Newcomer, nahlásím jej:
            if (this.ContainNewChilds)
                changes.NodeNewcomerList.AddRange(this.CreateNewcomerRows(clearIndicators));

            // Předám řízení do podřízených řádků:
            foreach (NodeInfo child in this.Childs)
                child.CollectChanges(changes, clearIndicators);
        }
        /// <summary>
        /// Vytvoří a vrátí seznam řádků, které jsou Newcomer.
		/// Ve vrácených řádcích je navíc uveden NextGId, aby byly řádky zatříděny do vizuální vrstvy
        /// </summary>
        /// <returns></returns>
        private IEnumerable<IDataRow> CreateNewcomerRows(bool clearIndicators)
        {
            List<IDataRow> result = new List<IDataRow>();

            List<NodeInfo> childs = this.ChildsSorted;
            int c = childs.Count;
            for (int n = 0; n < c; n++)
            {
                NodeInfo child = childs[n];
                if (!child.Newcomer) continue;

                // Najdu nejbližší příští node mezi childs, který není Newcomer = to je ten, který je Next (před něj se bude vkládat náš Newcomer Child):
                GID nextGId = GID.Empty;
                for (int x = n + 1; x < c; x++)
                {
                    if (!childs[x].Newcomer)
                    {
                        nextGId = childs[x].GId;
                        break;
                    }
                }

                // Náš řádek do výstupu, včetně klíče NextGId:
                result.Add(child.CreateRow(nextGId));

                if (clearIndicators)
                    child.Newcomer = false;
            }
            return result;
        }
        #endregion
        #region Přidání subnode a celé cesty
		/// <summary>
		/// Zajistí, že celá zadaná cesta bude v tomto nodu existovat (počínaje: první položka zadané cesty bude existovat v this.Childs).
		/// </summary>
		/// <param name="treePath"></param>
		internal NodeInfo AddPath(TreePath treePath, string traceType)
		{
			if (treePath == null) return null;                // Nezadaná cesta = vrátím null (nelze provést)
			TreePathItem treeItem = treePath.TopItem;         // Vrátí první neprázdnou položku
			if (treeItem == null) return this;                // Nic není = cesta je prázdná, vrátím sám sebe protože já jsem výsledkem přidávání cesty (rekurzivně stále tato metoda!!!)
			NodeInfo subNode;
			if (!this.TryGetChild(treeItem.Key, out subNode))
				subNode = this.AddChild(treeItem, traceType); // Pokud subnode neexistuje, přidá nový subnode do this

			return subNode.AddPath(treePath.PartAfter(treeItem), traceType);    // Zajistí totéž pro další část cesty, počínaje nalezeným/vytvořeným subnodem (dokud nějaká cesta zbude). Vrátí nejnižší prvek cesty.
		}
		/// <summary>
        /// Přidá do tohoto nodu další subnode.
        /// Vrací objekt tohoto nového subnode, aby se v něm mohly nastavit další vlastnosti.
        /// </summary>
        /// <param name="key"></param>
		internal NodeInfo AddChild(string key, string traceType)
        {
            NodeInfo child = new NodeInfo(this.Main, this, key);
			child.TraceType = traceType;
			this._AddChild(child);
            return child;
        }
        /// <summary>
        /// Přidá do tohoto nodu další subnode.
        /// Vrací objekt tohoto nového subnode, aby se v něm mohly nastavit další vlastnosti.
        /// </summary>
        /// <param name="key"></param>
        /// <param name="text"></param>
        /// <param name="rowGraph"></param>
		internal NodeInfo AddChild(string key, string[] texts, string traceType, RowGraphMode rowGraph, RowProperties properties)
        {
            NodeInfo child = new NodeInfo(this.Main, this, key, texts, rowGraph, properties);
			child.TraceType = traceType;
			this._AddChild(child);
			return child;
        }
        /// <summary>
        /// Přidá do tohoto nodu další subnode.
        /// Vrací objekt tohoto nového subnode, aby se v něm mohly nastavit další vlastnosti.
        /// Z položky treeItem přebírá všechny informace, které tato položka nese.
        /// </summary>
        /// <param name="treeItem"></param>
        /// <returns></returns>
		internal NodeInfo AddChild(TreePathItem treeItem, string traceType)
        {
            NodeInfo child = new NodeInfo(this.Main, this, treeItem);
			child.TraceType = traceType;
			this._AddChild(child);
            return child;
        }
        /// <summary>
        /// Přidá do tohoto nodu další subnode.
        /// Subnode dostává na vstupu.
        /// </summary>
        /// <param name="treeItem"></param>
        /// <returns></returns>
        internal void AddChild(NodeInfo child)
        {
			this._AddChild(child);
        }
		/// <summary>
		/// Přidá další subnode. Výkonná metoda. Zajišťuje hlídání změn.
		/// Ostatní metody pro přidávání subnodu musí volat tuto metodu, nesmějí vkládat nový node přímo do this.Childs!
		/// </summary>
		/// <param name="child"></param>
		private void _AddChild(NodeInfo child)
		{
			this.Childs.Add(child);
			this.Root.ContainAnyChange = true;
		}
        #endregion
        #region Root: unikátní objekt, který je instancován pouze v Root nodu, a z ostatních nodů je napřímo referencován (ne tedy dynamicky přes všechny parenty)
        /// <summary>
        /// Root: unikátní objekt, který je instancován pouze v Root nodu, a z ostatních nodů je napřímo referencován (ne tedy dynamicky přes všechny parenty).
        /// Obsahuje index všech nodů a referenci na majitele tree, a zajišťuje sběr dat o invalidaci nodů (při změnách obsahu).
        /// </summary>
        public RootInfo Root { get; private set; }
		/// <summary>
		/// Příznak, zda tento objekt je Root (true) nebo není root (false).
		/// </summary>
		public bool IsRoot { get; private set; }
        #endregion
        #region Vyhledání libovolně vnořeného subnodu na základě TreePath, na základě filtru
        /// <summary>
        /// Najde a vrátí SubNode tohoto nodu, jehož cesta je daná parametrem.
        /// Cesta obsahuje jednotlivé prvky (TreeNodeItem), jejichž Key zde postupně hledám ve svých Childs.
        /// </summary>
        /// <param name="treePath"></param>
        /// <returns></returns>
        internal NodeInfo FindSubNode(string treePath)
        {
            return FindSubNode(new TreePath(treePath));
        }
        /// <summary>
        /// Najde a vrátí SubNode tohoto nodu, jehož cesta je daná parametrem.
        /// Cesta obsahuje jednotlivé prvky (TreeNodeItem), jejichž Key zde postupně hledám ve svých Childs.
        /// </summary>
        /// <param name="treePath"></param>
        /// <returns></returns>
        internal NodeInfo FindSubNode(TreePath treePath)
        {
            TreePathItem topItem = treePath.TopItem;
            if (topItem == null)
                return this;            // Pokud nechci nic dalšího, vrátím sám sebe

            // Chci další subnode (mám jeho Key) - zkusím takový subnode najít v mých Childs:
            NodeInfo subNode;
            if (this.Childs.TryGetValue(topItem.Key, out subNode))
                // Pokud jsem subnode našel, nechám ho aby on našel svůj subnode, podle dané cesty subPath.
                // Pokud bude subPath prázdná, vrátí sám sebe:
                return subNode.FindSubNode(treePath.PartAfter(topItem));
            else
                // Pokud já hledaný Key nemám, vrátím null:
                return null;
        }
        /// <summary>
        /// Najde a vrátí SubNode tohoto nodu, jehož cesta je daná parametrem.
        /// </summary>
        /// <param name="treePath"></param>
        /// <returns></returns>
        internal NodeInfo FindSubNode(TreePathItem topItem)
        {
            if (topItem == null || topItem.IsEmpty)
                return null;            // Pokud nechci nic, vrátím null

            // Chci další subnode (mám jeho Key) - zkusím takový subnode najít v mých Childs:
            NodeInfo subNode;
            if (this.Childs.TryGetValue(topItem.Key, out subNode))
                // Pokud jsem subnode našel, nechám ho aby on našel svůj subnode, podle dané cesty subPath.
                // Pokud bude subPath prázdná, vrátí sám sebe:
                return subNode;
            else
                // Pokud hledaný Key nemám, vrátím null:
                return null;
        }
        /// <summary>
        /// Zkusí najít subnode podle klíče.
        /// </summary>
        /// <param name="key"></param>
        /// <param name="subNode"></param>
        /// <returns></returns>
        public bool TryGetChild(string key, out NodeInfo subNode)
        {
            return this.Childs.TryGetValue(key, out subNode);
        }
        /// <summary>
        /// Vrací seznam úplně všech nodů = this + Childs + jejich Childs rekurzivně.
        /// Dochází tedy k linearizaci stromu.
        /// Volitelně lze (jako parametr) zadat funkci, která bude nody filtrovat.
        /// </summary>
        /// <returns></returns>
        public List<NodeInfo> GetAllNodes()
        {
            List<NodeInfo> result = new List<NodeInfo>();
            this._GetAllNodes(node => true, result);
            return result;
        }
        /// <summary>
        /// Vrací seznam úplně všech nodů = this + Childs + jejich Childs rekurzivně.
        /// Dochází tedy k linearizaci stromu.
        /// Volitelně lze (jako parametr) zadat funkci, která bude nody filtrovat.
        /// </summary>
        /// <param name="predicate">Filtr, jemuž musí vyhovět nody, které se vkládají do výstupu</param>
        /// <returns></returns>
        public List<NodeInfo> GetAllNodes(Func<NodeInfo, bool> predicate)
        {
            List<NodeInfo> result = new List<NodeInfo>();
            this._GetAllNodes(predicate, result);
            return result;
        }
        /// <summary>
        /// Privátní metoda pro naplnění seznamu (parametr nodeList) těmi nody, které vyhoví podmínce predicate. Rekurzivně pro Childs.
        /// </summary>
        /// <param name="nodeList"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        private void _GetAllNodes(Func<NodeInfo, bool> predicate, List<NodeInfo> nodeList)
        {
            if (predicate == null || predicate(this))
                nodeList.Add(this);
            foreach (NodeInfo child in this.Childs)
                child._GetAllNodes(predicate, nodeList);
        }
        /// <summary>
        /// Zjistí, zda tento node má jaké svého přímého parenta (nebo podle volby "recursive" jakkoli vzdáleného parenta) takový node, jehož Key odpovídá zadanému.
        /// </summary>
        /// <param name="parentKey">Hledaný Key parenta, může být Case-Insensitive podle parametru caseInsensitive</param>
        /// <param name="recursive">Příznak, že mám hledat i v nadřízených parentech (true = hledat v nadřízených / false = testovat jen přímého nadřízeného)</param>
        /// <param name="caseInsensitive">Příznak, že Key se má porovnávat CaseInsensitive = bez ohledu na velikost písmen</param>
        /// <returns></returns>
        public bool IsChildOfParent(string parentKey, bool recursive, bool caseInsensitive)
        {
            if (this.IsRoot) return false;
            StringComparison sc = (caseInsensitive ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture);
            if (String.Equals(this.Parent.Key, parentKey, sc)) return true;
            if (!recursive) return false;
            return this.Parent.IsChildOfParent(parentKey, recursive, caseInsensitive);
        }
        #endregion
		#region Přidávání, vyhledání, smazání, invalidace elementů
		/// <summary>
        /// Přidá do sebe daný element.
		/// Pokud v sobě již má element shodného GIDu, přepíše do této pozie nově předaný objekt (aktualizace).
		/// Pokud je ještě nemá, vloží jej jako nový.
        /// </summary>
        /// <param name="element"></param>
        internal void AddElement(IDataElement element)
        {
			if (!element.GId.IsEmpty)
			{
				if (this.Elements.ContainsKey(element.GId))
					this.Elements[element.GId] = element;
				else
					this.Elements.Add(element.GId, element);

				this.InvalidateElements();
			}
        }
        /// <summary>
        /// Vrátí element pro daný GID, vrátí null pokud element pro daný GID neexistuje.
        /// </summary>
        /// <param name="elementGId"></param>
        /// <returns></returns>
        internal IDataElement FindElement(GID elementGId)
        {
            IDataElement element;
            if (this.Elements.TryGetValue(elementGId, out element))
                return element;
            return null;
        }
        /// <summary>
		/// Vrátí true, pokud v tomto nodu existuje element daného GIDu.
		/// </summary>
		/// <param name="elementGId"></param>
		/// <returns></returns>
		internal bool ContainElement(GID elementGId)
		{
			return this.Elements.ContainsKey(elementGId);
		}
		/// <summary>
		/// Odebere ze sebe daný element.
		/// Pokud zde není, nic se neděje, k chybě nedojde.
		/// Pokud zde je, odebere se a zajistí se invalidace elementů do vizuální vrstvy.
		/// </summary>
		/// <param name="elementGId"></param>
		internal void RemoveElement(GID elementGId)
		{
			if (this.Elements.ContainsKey(elementGId))
			{
				this.Elements.Remove(elementGId);
				this.InvalidateElements();
			}
		}
        /// <summary>
        /// Zahodí všechny svoje elementy, ale ne pro svoje Childs.
        /// Zajistí invalidaci dat do datové vrstvy.
        /// Pokud zde není, nic se neděje, k chybě nedojde.
        /// </summary>
        /// <param name="elementGId"></param>
        internal void ClearElements()
        {
            this.Elements.Clear();
            this.InvalidateElements();
        }
        /// <summary>
		/// Invaliduje elementy do vizuální vrstvy, ta si je poté znovu načte (dojde k tomu, že se nově vytvoří aktuální soupis elementů).
		/// </summary>
		internal void InvalidateElements()
		{
			// Pokud nejsem nováček (protože Nováček ještě není ve vizuální vrstvě), pak ohlásím že se změnily moje elementy:
			if (!this.Newcomer)
			{
				this.ContainChangeInThisElements = true;
				this.Root.ContainAnyChange = true;
			}
		}
        /// <summary>
        /// Zahodí všechny elementy z paměti.
        /// </summary>
        internal void MemoryFree()
        {
            this.Elements.Clear();
            this.InvalidateElements();

            foreach (NodeInfo child in this.Childs)
                child.MemoryFree();
        }
        #endregion
        #region Vytváření grafického řádku IDataRow (za sebe sama), vytváření sady řádků (za this.Childs), vytváření elementů (this.Elements)
        /// <summary>
        /// Vytvoří řádek za sebe
        /// </summary>
        /// <returns></returns>
        public virtual IDataRow CreateRow()
        {
			return CreateRow(GID.Empty, TimeRange.Empty, false);
        }
        /// <summary>
        /// Vytvoří řádek za sebe
        /// </summary>
        /// <returns></returns>
        public virtual IDataRow CreateRow(GID nextGId)
        {
			return CreateRow(nextGId, TimeRange.Empty, false);
		}
		/// <summary>
		/// Vytvoří řádek za sebe
		/// </summary>
		/// <returns></returns>
		protected virtual IDataRow CreateRow(GID nextGId, TimeRange preloadTimeRange, bool clearIndicators)
		{
            // Řádek Main = souhrn trace souborů:
            TraceGraphRow row = new TraceGraphRow()
            {
                GId = this.GId,
                NodeName = this.Key,
                HasSubNodes = (this.Childs.Count > 0 || ((this.RowDeclaration.Properties & RowProperties.DynamicSubNodeLoading) > 0)),
                RowGraph = this.RowDeclaration.RowGraph,
                RowHeightMultiplier = this.RowDeclaration.RowHeightMultiplier,
                ParentGId = (this.HasParent ? this.Parent.GId : GID.Empty),
                NextGId = nextGId,
                ActionOnRowDoubleClick = this.RowDeclaration.ActionOnRowDoubleClick,
                ActionOnElementDoubleClick = this.RowDeclaration.ActionOnElementDoubleClick,
                ShowPrevNextIcon = this.RowDeclaration.ShowPrevNextIcon,
                ReloadSubNodesOnExpandNode = this.RowDeclaration.ReloadSubNodesOnExpandNode,
                Icon = this.RowDeclaration.Icon,
				IconAlignment = System.Drawing.ContentAlignment.BottomCenter,
                RowColor = this.RowDeclaration.RowColor,
                RowHeightTotalMinPx = this.RowDeclaration.RowHeightTotalMinPx,
                RowHeightTotalMaxPx = this.RowDeclaration.RowHeightTotalMaxPx,
                RowHeightLineSolidPx = this.RowDeclaration.RowHeightLineSolidPx,
                RowHeightLineFragmentPx = this.RowDeclaration.RowHeightLineFragmentPx,
                RowHeightMinFragmentPx = this.RowDeclaration.RowHeightMinFragmentPx,
                RowHeightAddLineSolidRatio = this.RowDeclaration.RowHeightAddLineSolidRatio,
                RowHeightAddLineFragmentRatio = this.RowDeclaration.RowHeightAddLineFragmentRatio,
                Texts = this.Texts
            };

            // Pokud se NEMAJÍ elementy načítat dynamicky, musím je do řádku vložit nyní, a to všechny:
            if (!this.RowDeclaration.IsDynamicElementsLoading || (this.RowDeclaration.IsPreLoadingElements && !preloadTimeRange.IsEmpty))
            {	// Pokud node NEMÁ dynamicky načítané elementy, pak právě nyní vypíšu všechny zdejší elementy:
                row.ElementList = this.CreateElementList(TimeRange.Empty);
                if (!this.RowDeclaration.IsDynamicElementsLoading)
                    // Pokud pro tento řádek NE-platí dynamické donačítání elementů 
                    //  (tedy, když do řádku na začátku vložím všechny jeho elementy, a později už žádné nemohou přijít),
                    //  pak do řádku vepíšu AllElementsLoaded = true; => grafická vrstva si zapamatuje, že řádek je definitivně naplněný:
                    row.AllElementsLoaded = true;
            }
			else if (this.RowDeclaration.IsPreLoadingElements && !preloadTimeRange.IsEmpty)
			{	// Pokud mám elementy vložit do řádku rovnou při jeho tvorbě, udělám to teď:
				row.ElementList = this.CreateElementList(preloadTimeRange);
				row.ElementLoadedTimeRange = preloadTimeRange;
			}

			// Nyní jsem předal sám sebe jako řádek do vizuálního listu, od teď už nejsem nově příchozím, ale stálým obyvatelem listu.
			// Má to význam pro detekci změn v metodě this.CollectChanges().
			if (clearIndicators)
				this.Newcomer = false;

            return row;
        }
        /// <summary>
        /// Vytvoří sadu řádků za své Childs
        /// </summary>
        /// <returns></returns>
		internal IEnumerable<IDataRow> CreateChildRows()
		{
			return CreateChildRows(TimeRange.Empty);
		}
        /// <summary>
        /// Vytvoří sadu řádků za své Childs
        /// </summary>
        /// <returns></returns>
		internal IEnumerable<IDataRow> CreateChildRows(TimeRange preloadTimeRange)
        {
            List<IDataRow> result = new List<IDataRow>();

            // Třídění subnodes, tvorba řádků za node Childs:
            List<NodeInfo> childs = this.ChildsSorted;
            foreach (NodeInfo child in childs)
				result.Add(child.CreateRow(GID.Empty, preloadTimeRange, true));

			// Nastavím si proměnnou, že jsem svoje subnody již do grafické vrstvy odeslal:
			// To má ten důsledek, že pokud by se do mé kolekce Childs (anebo do kolekce Childs některého z mých Childs) přidal nějaký nový node,
			//  tak bude tento nový Child (Newcomer) přidán do změnového pole, viz metoda this.CollectChanges():
			this.ChildRowsCreated = true;

            return result;
        }
        /// <summary>
        /// Obsahuje (vždy vygeneruje a vrátí nový) seznam všech Childs, setříděný dle this.Properties
        /// </summary>
        internal List<NodeInfo> ChildsSorted
        {
            get
            {
                List<NodeInfo> childs = this.Childs.List;
                if (this.RowDeclaration.IsChildSortedKey)
                    childs.Sort(NodeInfo.CompareByKey);
                else if (this.RowDeclaration.IsChildSortedText)
                    childs.Sort(NodeInfo.CompareByName);
                return childs;
            }
        }
        /// <summary>
        /// Vytvoří a vrátí seznam elementů, vytvořených z tohoto nodu.
        /// Elementy ukládá do výstupního seznamu v podobě KeyValuePair, kde Key je klíč zdejšího řádku, a Value je element.
        /// </summary>
        /// <param name="timeRange"></param>
        /// <returns></returns>
        public virtual List<KeyValuePair<GID, IDataElement>> CreateRowElementList(TimeRange timeRange)
        {
            List<KeyValuePair<GID, IDataElement>> result = new List<KeyValuePair<GID, IDataElement>>();

            TimeRange readTimeRange = (this.RowDeclaration.IsPreLoadingElements ? TimeRange.Empty : timeRange);

            List<IDataElement> elements = this.CreateElementList(readTimeRange);
            foreach (IDataElement element in elements)
                result.Add(new KeyValuePair<GID, IDataElement>(this.GId, element));
 
            return result;
        }
        /// <summary>
        /// Vytvoří a vrátí seznam elementů, vytvořených z tohoto nodu
        /// </summary>
        /// <param name="timeRange"></param>
        /// <returns></returns>
        public virtual List<IDataElement> CreateElementList(TimeRange timeRange)
        {
            List<IDataElement> result = new List<IDataElement>();
            bool allElements = (timeRange.IsEmpty);
            int omitted = 0;
            foreach (IDataElement element in this.Elements.Values)
            {
                if (element != null && element.ElementVisible && (allElements || element.TimeRange.IsConjunct(timeRange)))
                    result.Add(element);
                else
                    omitted++;
            }
            return result;
        }
        #endregion
        #region Property nodu jakožto řádku v grafu, odsud se přebírá do vytvářeného objektu IDataRow
        /// <summary>
        /// Standardní deklarace řádku
        /// </summary>
        internal GraphRowDeclarationCls RowDeclaration { get; set; }
        public bool HasParent { get { return this.Parent != null && this.Parent.RowDeclaration.RowGraph != RowGraphMode.NoDrawingElements; } }
        /// <summary>
        /// Texty tohoto nodu
        /// </summary>
        public string[] Texts { get; set; }
        /// <summary>
        /// Vrací první text - víceméně pro třídění podle textu.
        /// </summary>
        public string TextFirst
        {
            get
            {
                if (this.Texts == null || this.Texts.Length == 0) return null;
                return this.Texts[0];
            }
        }
        /// <summary>
        /// Porovná dvě instance podle jejich prvního textu. Pro třídění.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static int CompareByName(NodeInfo a, NodeInfo b)
        {
            return String.Compare(a.TextFirst, b.TextFirst);
        }
        #endregion
    }
    #endregion
    #region RootInfo : třída, která je v každém systému stromu (Nodes) instancována pouze jedenkrát v Root nodu, a z ostatních nodů je napřímo referencována (ne tedy dynamicky přes všechny parenty).
    /// <summary>
    /// RootInfo : třída, která je v každém systému stromu (Nodes) instancována pouze jedenkrát v Root nodu, a z ostatních nodů je napřímo referencována (ne tedy dynamicky přes všechny parenty).
    /// Obsahuje index všech nodů a referenci na majitele tree, a zajišťuje sběr dat o invalidaci nodů (při změnách obsahu).
    /// </summary>
    public class RootInfo
    {
        #region Konstrukce
        public RootInfo(NodeInfo rootNode)
        {
            this.RootNode = rootNode;
            this.NodeIdx = new Dictionary<GID, NodeInfo>();
        }
        /// <summary>
        /// Kořenový node stromu, o který se tento RootInfo stará.
        /// </summary>
        public NodeInfo RootNode { get; private set; }
        /// <summary>
        /// Index nodů. Dokáže snadno a rychle nalézt node podle jeho GIDu.
        /// Index je udržován tak, že každý node po svém vytvoření se sám přidá do indexu, pomocí metody NodeInfo.RootNode.AddToIndex(this).
        /// </summary>
        public Dictionary<GID, NodeInfo> NodeIdx { get; private set; }
        #endregion
        #region Index všech nodů (lineární Dictionary) : přidání, odebrání, hledání
        /// <summary>
        /// Přidá node do indexu
        /// </summary>
        /// <param name="node"></param>
        public void AddToIndex(NodeInfo node)
        {
            this.NodeIdx.Add(node.GId, node);
        }
        /// <summary>
        /// Odebere node z indexu
        /// </summary>
        /// <param name="node"></param>
        public void RemoveFromToIndex(NodeInfo node)
        {
            if (this.NodeIdx.ContainsKey(node.GId))
                this.NodeIdx.Remove(node.GId);
        }
        /// <summary>
        /// Najde a vrátí node podle jeho GIDu.
        /// Pokud nenajde, vrátí null.
        /// </summary>
        /// <param name="gId"></param>
        /// <returns></returns>
        public NodeInfo FindNode(GID gId)
        {
            NodeInfo nodeInfo;
            if (this.NodeIdx.TryGetValue(gId, out nodeInfo))
                return nodeInfo;
            return null;
        }

        #endregion
        #region Správa nových nodů a nodů se změnu v elementech. Tvorba informace o změnách.
		/// <summary>
		/// Metoda fyzicky projde celou svoji hierarchii všech nodů 
		/// a vrátí přehled aktuálních změn, které by mohly zajímat vizuální vrstvu.
		/// </summary>
		/// <param name="clearIndicators"></param>
		/// <returns></returns>
        public NodeChangeCollect CollectChanges(bool clearIndicators)
        {
            NodeChangeCollect changes = new NodeChangeCollect();
			
			// Pokud obsahujeme nějakou změnu(změny), najdeme je:
			if (this.ContainAnyChange)
			{
				this.RootNode.CollectChanges(changes, clearIndicators);

				// Pokud máme nulovat indikátory, nulujeme je:
				if (clearIndicators)
					this.ContainAnyChange = false;
			}

            return changes;
        }
		/// <summary>
		/// Globální příznak, že tato kolekce obsahuje jakoukoli změnu.
		/// Příznak je nulován při dokončení metody this.CollectChanges(true).
		/// Příznak je nastaven ze všech metod NodeInfo, které mění obsah Tree.
		/// Všechny nody mají přímý přístup na Root (tj. tento objekt).
		/// </summary>
		public bool ContainAnyChange { get; internal set; }
        #endregion
    }
    #endregion
    #region TreePath : celá cesta k nodu, serializace. TreePathItem : jedna úroveň cesty TreePath.
    /// <summary>
    /// TreePath : cesta k nodu, serializace, deserializace
    /// </summary>
    public class TreePath
    {
        public TreePath(bool absolute)
        {
            this.AbsolutePath = absolute;
            this.PathItems = new LinkedList<TreePathItem>();
        }
        public TreePath(string serialized)
        {
            this.PathItems = new LinkedList<TreePathItem>();
            this.Serial = serialized;
        }
        public override string ToString()
        {
            return this.Serial;
        }
        /// <summary>
        /// Příznak, zda uložená cesta je true=absolutní nebo false=relativní
        /// </summary>
        public bool AbsolutePath { get; private set; }
        /// <summary>
        /// Položky cesty
        /// </summary>
        protected LinkedList<TreePathItem> PathItems { get; private set; }
        /// <summary>
        /// Přidá do cesty další položku
        /// </summary>
        /// <param name="serialized"></param>
        public void AddItem(string serialized)
        {
            this.AddItem(new TreePathItem(serialized));
        }
        /// <summary>
        /// Přidá do cesty další položku
        /// </summary>
        /// <param name="serialized"></param>
        public void AddItem(TreePathItem item)
        {
            if (item != null)
            {
                if (item.IsRoot)
                {   // Pokud položka tvrdí, že je Root (to teoreticky může tvrdit i prázdná položka, proto to řeším před testem IsEmpty):
                    if (this.PathItems.Count == 0)
                        // Když je první, tak jí to uvěřím:
                        this.AbsolutePath = true;
                    else
                        // Když není první, tak kecá a rozmluvím jí to:
                        item.IsRoot = false;
                }
                if (!item.IsEmpty)
                    this.PathItems.AddLast(item);
            }
        }
        /// <summary>
        /// true, pokud je tento path prázdný (neobsahuje žádný platný prvek)
        /// </summary>
        public bool IsEmpty
        {
            get
            {
                if (this.AbsolutePath) return false;            // Absolutní cesta není prázdná.
                if (this.PathItems != null)
                {
                    foreach (TreePathItem item in this.PathItems)
                    {
                        if (!item.IsEmpty) return false;
                    }
                }
                return true;
            }
        }
        /// <summary>
        /// Serializovaný formát. Funguje jak čtení = serializace, tak zápis = deserializace (parsování).
        /// Formát je: "item1\item2\...\itemX", počet položek je libovolný.
        /// </summary>
        public string Serial
        {
            get
            {
                string result = (this.AbsolutePath ? ABSOLUTE_PATH_START : "");        // Absolutní cesta začíná zpětným lomítkem
                foreach (TreePathItem item in this.PathItems)
                {
                    if (!item.IsEmpty)
                        result += (result.Length == 0 ? "" : PATH_LEVEL_SEPARATOR) + item.Serial;
                }
                return result;
            }
            set
            {
                this.AbsolutePath = false;
                this.PathItems = new LinkedList<TreePathItem>();
                if (value != null)
                {
                    string text = value.Trim();
                    this.AbsolutePath = text.StartsWith(ABSOLUTE_PATH_START);
                    if (this.AbsolutePath)
                        text = (text.Length == ABSOLUTE_PATH_START.Length ? "" : text.Substring(ABSOLUTE_PATH_START.Length));
                    string[] serials = text.Split(new string[] { PATH_LEVEL_SEPARATOR }, StringSplitOptions.RemoveEmptyEntries);
                    foreach (string serial in serials)
                    {
                        TreePathItem item = new TreePathItem(serial);
                        if (!item.IsEmpty)
                            this.PathItems.AddLast(item);
                    }
                }
            }
        }
        /// <summary>
        /// Pole jednotlivých složek cesty. Na indexu [0] je nejvyšší položka.
        /// Pozor: pokaždé se generuje nové pole.
        /// Objekty TreePathItem ve výstupním poli jsou klony objektů TreePathItem v this.
        /// </summary>
        public List<TreePathItem> Items
        {
            get
            {
                List<TreePathItem> items = new List<TreePathItem>();
                foreach (TreePathItem thisItem in this.PathItems)
                {
                    TreePathItem item = thisItem.Clone();
                    if (!item.IsEmpty)
                    {
                        items.Add(item);
                        if (items.Count == 1 && this.AbsolutePath && !item.IsRoot)
                            item.IsRoot = true;
                    }
                }
                return items;
            }
        }
        /// <summary>
        /// Vrátí první neprázdný item.
        /// </summary>
        /// <returns></returns>
        public TreePathItem TopItem
        {
            get
            {
                LinkedListNode<TreePathItem> linkItem = this.PathItems.First;
                while (linkItem != null && linkItem.Value != null && linkItem.Value.IsEmpty)
                    linkItem = linkItem.Next;
                return (linkItem == null ? null : linkItem.Value);
            }
        }
        /// <summary>
        /// Vrátí poslední neprázdný item.
        /// </summary>
        /// <returns></returns>
        public TreePathItem LastItem
        {
            get
            {
                LinkedListNode<TreePathItem> linkItem = this.PathItems.Last;
                while (linkItem != null && linkItem.Value != null && linkItem.Value.IsEmpty)
                    linkItem = linkItem.Previous;
                return (linkItem == null ? null : linkItem.Value);
            }
        }
        /// <summary>
        /// Vrátí následující neprázdný item za danou položkou.
        /// </summary>
        /// <returns></returns>
        public TreePathItem NextItem(TreePathItem currentItem)
        {
            LinkedListNode<TreePathItem> currentLinkItem = this.FindLinkItem(currentItem);   // najdu odpovídající Link položku k dané Value (hledám ReferenceEqual)
            if (currentLinkItem == null) return null;
            LinkedListNode<TreePathItem> linkItem = currentLinkItem.Next;                    // rovnou vezmu Next
            while (linkItem != null && linkItem.Value != null && linkItem.Value.IsEmpty)
                linkItem = linkItem.Next;
            return (linkItem == null ? null : linkItem.Value);
        }
        /// <summary>
        /// Vrátí předchozí neprázdný item před danou položkou.
        /// </summary>
        /// <returns></returns>
        public TreePathItem PreviousItem(TreePathItem currentItem)
        {
            LinkedListNode<TreePathItem> currentLinkItem = this.FindLinkItem(currentItem);   // najdu odpovídající Link položku k dané Value (hledám ReferenceEqual)
            if (currentLinkItem == null) return null;
            LinkedListNode<TreePathItem> linkItem = currentLinkItem.Previous;                // rovnou vezmu Previous
            while (linkItem != null && linkItem.Value != null && linkItem.Value.IsEmpty)
                linkItem = linkItem.Previous;
            return (linkItem == null ? null : linkItem.Value);
        }
        /// <summary>
        /// Vrátí nový objekt TreePath, který vznikne jako pravý odstřižek (ve směru vpravo, ke konci) této cesty za daným prvkem.
        /// </summary>
        /// <param name="currentItem"></param>
        /// <returns></returns>
        internal TreePath PartAfter(TreePathItem currentItem)
        {
            TreePath result = new TreePath(false);                 // Zůstatek cesty vpravo (After) je vždy relativní (false)
            LinkedListNode<TreePathItem> currentLinkItem = this.FindLinkItem(currentItem);   // najdu odpovídající Link položku k dané Value (hledám ReferenceEqual)
            if (currentLinkItem != null)
            {
                LinkedListNode<TreePathItem> linkItem = currentLinkItem.Next;                // rovnou vezmu Next
                while (linkItem != null)
                {
                    if (linkItem.Value != null && !linkItem.Value.IsEmpty)
                        result.PathItems.AddLast(linkItem.Value.Clone());
                    linkItem = linkItem.Next;
                }
            }
            return result;
        }
        /// <summary>
        /// Vrátí nový objekt TreePath, který vznikne jako levý odstřižek (v směru vlevo, k začátku) této cesty před daným prvkem.
        /// Cestu samozřejmě neobrazí.
        /// </summary>
        /// <param name="currentItem"></param>
        /// <returns></returns>
        internal TreePath PartBefore(TreePathItem currentItem)
        {
            TreePath result = new TreePath(this.AbsolutePath);     // Zůstatek cesty vlevo (Before) je vždy absolutní, pokud je absolutní this
            LinkedListNode<TreePathItem> currentLinkItem = this.FindLinkItem(currentItem);   // najdu odpovídající Link položku k dané Value (hledám ReferenceEqual)
            if (currentLinkItem != null)
            {
                LinkedListNode<TreePathItem> linkItem = currentLinkItem.Previous;
                while (linkItem != null)
                {
                    if (linkItem.Value != null && !linkItem.Value.IsEmpty)
                        result.PathItems.AddFirst(linkItem.Value.Clone());
                    linkItem = linkItem.Previous;
                }
            }
            return result;
        }
        /// <summary>
        /// Najde LinkedItem k dané položce.
        /// </summary>
        /// <param name="currentItem"></param>
        /// <returns></returns>
        internal LinkedListNode<TreePathItem> FindLinkItem(TreePathItem currentItem)
        {
            if (currentItem == null) return null;
            return this.PathItems.FindByPredicate(i => Object.ReferenceEquals(i, currentItem));
        }
        /// <summary>
        /// String, který uvozuje absolutní cestu ve stringovém vyjádření. Jde o zpětné lomítko.
        /// </summary>
        public const string ABSOLUTE_PATH_START = "\\";
        /// <summary>
        /// String, který odděluje jednotlivé složky cesty. Jde o zpětné lomítko.
        /// </summary>
        public const string PATH_LEVEL_SEPARATOR = "\\";
        /// <summary>
        /// Metoda určí, zda předaná cesta je absolutní (vrátí true) nebo je relativní (vrátí false).
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static bool IsAbsolute(string path)
        {
            if (path.IsEmpty()) return false;
            string t = path.Trim();
            return (path.StartsWith(ABSOLUTE_PATH_START));
        }
    }
    /// <summary>
    /// Jedna položka cesty TreePath
    /// </summary>
    public class TreePathItem
    {
        public TreePathItem()
        { }
        public TreePathItem(string serialized)
        {
            this.Serial = serialized;
        }
        public TreePathItem(string key, string text, GraphRowDeclarationCls rowDeclaration)
        {
            this.IsRoot = false;
            this.Key = GetKeyDetectRoot(key);
            this.Text = text;
            this.RowDeclaration = rowDeclaration;
        }
        public override string ToString()
        {
            return this.Serial;
        }
        /// <summary>
        /// Klíč nodu. Beze změny velikosti písmen.
        /// </summary>
        public string Key { get; set; }
        /// <summary>
        /// Text vypisovaný v nodu
        /// </summary>
        public string Text { get; set; }
        /// <summary>
        /// Příznak, že tato položka je Root
        /// </summary>
        public bool IsRoot { get; set; }
        /// <summary>
        /// Deklarace řádku.
        /// Pozor: nepřežije serializaci.
        /// </summary>
        public GraphRowDeclarationCls RowDeclaration { get; set; }
        /// <summary>
        /// true, pokud je tento item prázdný (jeho Key je null nebo empty)
        /// </summary>
        public bool IsEmpty
        {
            get { return String.IsNullOrEmpty(this.Key); }
        }
        /// <summary>
        /// Serializovaný formát. Funguje jak čtení = serializace, tak zápis = deserializace (parsování).
        /// Formát je: "key:text", přičemž text je nepovinný.
        /// </summary>
        public string Serial
        {
            get
            {
                string result = "";
                if (!String.IsNullOrEmpty(this.Key))
                    result = this.Key.RemoveChars(":\\");
                if (!String.IsNullOrEmpty(this.Text))
                    result += ":" + this.Text.RemoveChars(":\\");
                return result;
            }
            set
            {
                this.Key = null;
                this.Text = null;
                this.IsRoot = false;
                this.RowDeclaration = null;
                string text = value;
                if (text != null)
                {
                    text = text.Trim();
                    string[] items = text.Split(':');
                    this.Key = GetKeyDetectRoot(items[0]);
                    if (items.Length > 1)
                        this.Text = items[1];
                }
            }
        }
        /// <summary>
        /// Metoda v daném textu pozná úvodní znak Root.
        /// Pokud tam je, pak nastaví this.IsRoot = true, a vrátí text za ním.
        /// POkud tam není, pak this.IsRoot ponechá a vrátí vstupní text.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        protected string GetKeyDetectRoot(string key)
        {
            if (key == null) return null;
            string text = key.Trim();
            if (text.StartsWith(TreePath.ABSOLUTE_PATH_START))
            {
                this.IsRoot = true;
                text = (text.Length == TreePath.ABSOLUTE_PATH_START.Length ? "" : text.Substring(TreePath.ABSOLUTE_PATH_START.Length));
            }
            return text;
        }
        /// <summary>
        /// Vrátí klon sebe sama
        /// </summary>
        /// <returns></returns>
        public TreePathItem Clone()
        {
            return (TreePathItem)(this.MemberwiseClone());
        }
    }
    #endregion
    #region NodeChangeCollect : sbírka dat o změnách v celém stromu nodů. Vytváří ji RootInfo, plní ji postupně všechny NodeInfo.
    /// <summary>
    /// NodeChangeCollect : sbírka dat o změnách v celém stromu nodů. Vytváří ji RootInfo, plní ji postupně všechny NodeInfo.
    /// </summary>
    public class NodeChangeCollect
    {
		public NodeChangeCollect()
		{
			this.NodeNewcomerList = new List<IDataRow>();
			this.NodeWithElementChangedList = new List<GID>();
		}
		public override string ToString()
		{
			return "Newcomers:" + this.NodeNewcomerList.Count.ToString() + "; ElementChanged:" + this.NodeWithElementChangedList.Count.ToString();
		}
		/// <summary>
		/// Počet změn celkem, pro účely debugování
		/// </summary>
		public int TotalChangeCnt { get { return this.NodeNewcomerList.Count + this.NodeWithElementChangedList.Count; } }
        /// <summary>
        /// Seznam nodů, které jsou nové, a které je třeba vložit do vizuální vrstvy.
        /// Řádky v sobě nesou informaci o ParentGId, o NextGId a o svém GIDu. Rovněž mohou nést svoje elementy.
        /// </summary>
        public List<IDataRow> NodeNewcomerList { get; private set; }
		/// <summary>
		/// Seznam nodů, jejichž některé elementy byly změněny, a je třeba je znovu načíst do vizuální vrstvy
		/// </summary>
		public List<GID> NodeWithElementChangedList { get; private set; }
	}
	#endregion
}
